using System;
using System.Collections;

namespace Neural
{
	/// <summary>
	/// Multi layer feed-forward network with online back-propagation learning
	/// </summary>
	public class BackProp: FeedForward
	{
	
		#region fields

		private bool b_IsBatch;

		#endregion
		
		#region properties

		/**************************************************************/
		/*                      Properties
		/**************************************************************/

		#endregion

		#region constructors

		/**************************************************************/
		/*                      Constructors
		/**************************************************************/

		public BackProp(int inputs, int outputs): base(inputs, null, outputs)
		{
			b_IsBatch = false;
			m_Error = 0;
		}

		public BackProp(int inputs, int[] layers, int outputs): base(inputs, layers, outputs)
		{
			b_IsBatch = false;
			m_Error = 0;
		}
		
		#endregion

		#region methods

		/**************************************************************/
		/*                      Methods
		/**************************************************************/

		/// <summary>
		/// Prepares network for before training one epoch
		/// </summary>
		public override void BeginEpoch()
		{
            if (b_IsBatch == true)
				throw new ArgumentException("Epoch already started");
			b_IsBatch = true;

			/*
			 * Clear network error 
			 */
			m_Error = 0;

			/*
			* Prepare every neuron for new epoch
			*/
			foreach (Layer layer in p_Layers)
				layer.ResetWeights();
		}

		/// <summary>
		/// End training current epoch
		/// </summary>
		public override void EndEpoch()
		{
            if (b_IsBatch == true)
			{
				b_IsBatch = false;

				/*
				* Update all weight vectors
				*/ 
				foreach (Layer layer in p_Layers)
					layer.ApplyWeights(m_Rate);
			}
		}

		/// <summary>
		/// Online back-propagation learning (One epoch)
		/// </summary>
		public override double Train()
		{
			/*
			 * Calcualte deltas for output and hidden layers 
			 * working backwards
			 */ 
			Layer upperLayer = null;
			Layer currentLayer = null;
			for (int i = p_Layers.Count - 1; i >= 0; i--)
			{
				currentLayer = ((Layer)p_Layers[i]);
				currentLayer.CalcDeltas(m_Targets, upperLayer);
				upperLayer = currentLayer;
			}

			/*
			 * After deltas have been calculated, update weight vectors
			 */ 
			double[] inputs = m_Inputs;
			foreach (Layer layer in p_Layers)
			{
				if (b_IsBatch)
					layer.CollectWeights(inputs, m_Rate);
				else
					layer.UpdateWeights(inputs, m_Rate, m_Momentum);
				inputs = layer.Outputs;
			}

			/*
			 * Finally, calculate network error for this epoch 
			 * (Sum of squared errors)
			 */ 
			if (b_IsBatch != true)
				m_Error = 0;
			
			for (int i = 0; i < m_Outputs.Length; i++)
			{
				double diff = m_Targets[i] - m_Outputs[i];
				m_Error += (diff * diff);
			}
			m_Error *= 0.5;
			
			return m_Error;
		}

		#endregion

	}
}
